﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Windows.Forms;
using CapstoneProjectAEReader.DataAccess.DBAccess;
using CapstoneProjectAEReader.Entities.Models;
using CapstoneProjectAEReader.GUIs;
using CapstoneProjectAEReader.GUIs.Others;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OAuth2;
using Google.Apis.Authentication;
using Google.Apis.Authentication.OAuth2;
using Google.Apis.Authentication.OAuth2.DotNetOpenAuth;
using Google.Apis.Drive.v2;
using Google.Apis.Drive.v2.Data;
using Google.Apis.Util;
using File = Google.Apis.Drive.v2.Data.File;
using CapstoneProjectAEReader.Common.Utility;
using ProgressBar = CapstoneProjectAEReader.GUIs.Others.ProgressBar;
using Utilities = CapstoneProjectAEReader.Common.Utility.Utilities;

namespace CapstoneProjectAEReader.Business.Others
{
    public class SyncManager
    {
        #region Attributes

        private NoteDetail _noteDetail;
        //0: google drive, 1: DB
        private int _mode;
        private const string GoogleStartUpload = "googleStartUpload";
        private const string UploadDone = "uploadDone";
        private const string UploadProblem = "uploadProblem";
        private const string DownloadDone = "downloadDone";
        private const string DownloadProblem = "downloadProblem";
        private const string AuthcodeWrong = "authcodeWrong";
        private DriveService _service = null;
        private IAuthenticator _auth = null;
        private const string SyncData = "syncDB";
        private const string SyncDataErr = "syncDbError";
        // Volatile is used as hint to the compiler that this data
        // member will be accessed by multiple threads.
        private volatile bool _shouldStop;


        public String AuthCode
        {
            get { return _authCode; }
            set { _authCode = value; }
        }

        private IAuthorizationState _state = null;

        public IAuthorizationState State
        {
            get { return _state; }
            set { _state = value; }
        }

        private String _authCode = "";

        private int _continueCheck = 1;

        public int ContinueCheck
        {
            get { return _continueCheck; }
            set { _continueCheck = value; }
        }

        #endregion

        #region Methods

        public void RequestStop()
        {
            _shouldStop = true;
        }

        public SyncManager(int mode)
        {
            _mode = mode;
            if (_mode == 0)
            {
                // Register the authenticator and create the service
                var provider = new NativeApplicationClient(GoogleAuthenticationServer.Description, Constants.ClientId,
                                                           Constants.ClientSecret);
                _auth = new OAuth2Authenticator<NativeApplicationClient>(provider, GetAuthorization);
                _service = new DriveService(_auth);
            }
        }

        #region SyncDB

        public void SyncEbookGroup(DTO baseDto, DTO dtoImport)
        {
            bool blnAdd = true;
            for (int i = 0; i < dtoImport.EbookGroup.Count; i++)
            {
                for (int j = 0; j < baseDto.EbookGroup.Count; j++)
                {
                    blnAdd = true;
                    if (dtoImport.EbookGroup.ElementAt(i).Groupname.Equals(baseDto.EbookGroup.ElementAt(j).Groupname))
                    {
                        blnAdd = false;
                        break;
                    }
                }
                if (blnAdd)
                {
                    EbookGroup ebookGroupImport = new EbookGroup();
                    ebookGroupImport = dtoImport.EbookGroup.ElementAt(i);
                    DAO dao = new DAO(baseDto);
                    dao.InsertEbookGroup(ebookGroupImport);
                }
            }
        }

        public void SyncNote(DTO dto1, DTO dtoImport)
        {
            bool blnAdd = true;
            DTO dtoTemp;
            dtoTemp = dto1;
            DAO dao = new DAO(dto1);
            for (int i = 0; i < dtoImport.Note.Count; i++)
            {
                for (int j = 0; j < dtoTemp.Note.Count; j++)
                {
                    blnAdd = true;
                    if (dtoImport.Note.ElementAt(i).NoteContent.Equals(dtoTemp.Note.ElementAt(j).NoteContent))
                    {
                        blnAdd = false;
                        break;
                    }
                }
                if (blnAdd)
                {
                    Note note = new Note();
                    note = dtoImport.Note.ElementAt(i);
                    dao.InsertNote(note);
                }
            }
        }

        public void SyncNoteDetail(DTO dto1, DTO dtoImport)
        {
            bool blnAdd = true;
            DTO dtoTemp;
            dtoTemp = dto1;
            DAO dao1 = new DAO(dto1);
            DAO daoImport = new DAO(dtoImport);
            for (int i = 0; i < dtoImport.NoteDetail.Count; i++)
            {
                NoteDetail noteDetail = new NoteDetail();
                noteDetail = dtoImport.NoteDetail.ElementAt(i);

                NoteDetail noteDetailtemp = new NoteDetail();
                noteDetailtemp = noteDetail;
                //get new bookID
                Ebook ebook = new Ebook();
                ebook = daoImport.GetEbookById(noteDetailtemp.Bookid);
                noteDetailtemp.Bookid = dao1.SelectEbook(ebook.Title, ebook.Author).Id;

                //get new NoteID
                Note note = new Note();
                note = daoImport.GetNoteById(noteDetailtemp.Noteid);
                noteDetailtemp.Noteid = dao1.GetNote(note.NoteContent).NoteId;
                if (dao1.CheckNoteDetailExist(noteDetailtemp.Noteid, noteDetailtemp.Bookid))
                {
                    NoteDetail selectedNd = dao1.GetNoteDetail(noteDetailtemp.Noteid, noteDetailtemp.Bookid);
                    selectedNd.Description += Constants.CommonCharacters.NextLine + noteDetailtemp.Description;
                    dao1.UpdateNoteDetail(selectedNd);
                }
                else
                {
                    dao1.InsertNoteDetail(noteDetailtemp);
                }
            }
        }

        public DTO SyncDB(DTO dto)
        {
            DTO rtDto = new DTO();
            rtDto = dto;
            DTO _dto = dto;
            FileHandler _fileHandler = new FileHandler();
            bool blnAdd = true;
            Dictionary<int, int> dicEbooks = new Dictionary<int, int>();
            Dictionary<int, int> dicNote = new Dictionary<int, int>();
            CapstoneProjectAEReader.GUIs.LibraryGUI.Message tempWindow = null;
            try
            {
                FolderBrowserDialog folderBrowserDg = new FolderBrowserDialog();
                string strEbookImportPath;
                if (folderBrowserDg.ShowDialog() == DialogResult.OK)
                {
                    _dto = _fileHandler.ReadFile(Constants.StorageStructure.ApplicationData);
                    DTO dtoImport =
                        _fileHandler.ReadFile(folderBrowserDg.SelectedPath + Constants.CommonCharacters.SlashString +
                                              Constants.DatFileName);
                    DTO dtoImportTemp1 =
                        _fileHandler.ReadFile(folderBrowserDg.SelectedPath + Constants.CommonCharacters.SlashString +
                                              Constants.DatFileName);
                    DTO dtoImportTemp2 =
                        _fileHandler.ReadFile(folderBrowserDg.SelectedPath + Constants.CommonCharacters.SlashString +
                                              Constants.DatFileName);
                    DTO dtoImportTemp3 =
                        _fileHandler.ReadFile(folderBrowserDg.SelectedPath + Constants.CommonCharacters.SlashString +
                                              Constants.DatFileName);
                    strEbookImportPath = folderBrowserDg.SelectedPath + Constants.CommonCharacters.SlashString +
                                         Constants.StorageStructure.Ebook;
                    //sync
                    tempWindow =
                        new CapstoneProjectAEReader.GUIs.LibraryGUI.Message(ResourceReader.GetResourceContent(SyncData));
                    tempWindow.Show();
                    if (dtoImport.Ebooks.Count > 0)
                    {
                        for (int i = 0; i < dtoImport.Ebooks.Count; i++)
                        {
                            for (int j = 0; j < _dto.Ebooks.Count; j++)
                            {
                                blnAdd = true;
                                if (dtoImport.Ebooks.ElementAt(i).Author.Equals(_dto.Ebooks.ElementAt(j).Author) &&
                                    dtoImport.Ebooks.ElementAt(i).Title.Equals(_dto.Ebooks.ElementAt(j).Title))
                                {
                                    blnAdd = false;
                                    break;
                                }
                            }
                            if (blnAdd)
                            {
                                Ebook ebookImport = new Ebook();
                                ebookImport = dtoImport.Ebooks.ElementAt(i);
                                ebookImport.Path = Constants.StorageStructure.EbookFolder +
                                                   Constants.CommonCharacters.SlashString + ebookImport.Title +
                                                   ebookImport.Type;
                                ebookImport.BookmarkPath = Constants.StorageStructure.EbookFolder +
                                                           Constants.CommonCharacters.SlashString + ebookImport.Title +
                                                           Constants.XmlExtension;
                                DAO dao = new DAO(_dto);
                                dao.InsertEbook(ebookImport);
                                dicEbooks.Add(dtoImport.Ebooks.ElementAt(i).Id,
                                              _dto.Ebooks.ElementAt(_dto.Ebooks.Count - 1).Id);
                                Utilities.CopyFile(
                                    strEbookImportPath + Constants.CommonCharacters.SlashString + ebookImport.Title +
                                    ebookImport.Type,
                                    Constants.StorageStructure.EbookFolder + Constants.CommonCharacters.SlashString +
                                    ebookImport.Title + ebookImport.Type);
                                if (
                                    System.IO.File.Exists(strEbookImportPath + Constants.CommonCharacters.SlashString +
                                                          ebookImport.Title +
                                                          Constants.XmlExtension))
                                {
                                    Utilities.CopyFile(
                                        strEbookImportPath + Constants.CommonCharacters.SlashString + ebookImport.Title +
                                        Constants.XmlExtension,
                                        Constants.StorageStructure.EbookFolder + Constants.CommonCharacters.SlashString +
                                        ebookImport.Title + Constants.XmlExtension);
                                }
                                if (
                                    System.IO.File.Exists(strEbookImportPath + Constants.CommonCharacters.SlashString +
                                                          ebookImport.Title +
                                                          Constants.PngExtension))
                                {
                                    Utilities.CopyFile(
                                        strEbookImportPath + Constants.CommonCharacters.SlashString + ebookImport.Title +
                                        Constants.PngExtension,
                                        Constants.StorageStructure.EbookFolder + Constants.CommonCharacters.SlashString +
                                        ebookImport.Title + Constants.PngExtension);
                                }
                            }
                        }
                    }

                    SyncEbookGroup(_dto, dtoImportTemp1);
                    SyncNote(_dto, dtoImportTemp2);
                    SyncNoteDetail(_dto, dtoImportTemp3);
                    tempWindow.Close();
                    rtDto = _dto;
                }
            }
            catch (Exception ex)
            {
                if (tempWindow != null)
                {
                    tempWindow.Close();
                }
                Utilities.ShowErrorMesBox(ResourceReader.GetResourceContent(SyncDataErr));
            }
            return rtDto;
        }

        #endregion

        #region auth

        private IAuthorizationState GetAuthorization(NativeApplicationClient arg)
        {
            if (_authCode.IsNullOrEmpty())
            {
                _continueCheck = 1;
                // Get the auth URL:
                _state = new AuthorizationState(new[] {DriveService.Scopes.Drive.GetStringValue()});
                _state.Callback = new Uri(NativeApplicationClient.OutOfBandCallbackUrl);
                Uri authUri = arg.RequestUserAuthorization(_state);
                // Request authorization from the user (by opening a browser window):
                Process.Start(authUri.ToString());
                SyncAuthGUI authForm = new SyncAuthGUI(this);
                authForm.ShowDialog();
            }

            // Retrieve the access token by using the authorization code:

            if (_continueCheck == 0)
            {
                return null;
            }
            Utilities.ShowWarningMesbox(ResourceReader.GetResourceContent(GoogleStartUpload));
            return arg.ProcessUserAuthorization(_authCode, _state);
        }

        #endregion

        #region upload

        /// <summary>
        /// Upload App data to Google Drive
        /// </summary>
        public void UploadHandler(DTO dto)
        {
            try
            {
                DAO dao = new DAO(dto);
                // check file exist
                List<File> lstfile = new List<File>();
                while (true)
                {
                    lstfile = RetrieveAllFiles();
                    if (lstfile == null && _continueCheck == 1)
                    {
                        _authCode = Constants.CommonCharacters.Empty;
                    }
                    else
                    {
                        break;
                    }
                }
                if (_continueCheck == 1)
                {
                    Boolean ExistFlag = false;
                    foreach (File file in lstfile)
                    {
                        if (file.Title == Constants.StorageStructure.RootFolder)
                        {
                            // file is exist
                            ExistFlag = true;

                            // Get all file in AEreaderdatafolder
                            List<File> allfile = PrintFilesInFolder(file.Id);
                            foreach (File oldfolder in allfile)
                            {
                                if (oldfolder.Title == Constants.StorageStructure.Ebook)
                                {
                                    List<File> allbookdetailfile = PrintFilesInFolder(oldfolder.Id);
                                    foreach (File oldbookdetailfile in allbookdetailfile)
                                    {
                                        _service.Children.Delete(oldfolder.Id, oldbookdetailfile.Id).Fetch();
                                    }

                                    if (Directory.Exists(Constants.StorageStructure.EbookFolder))
                                    {
                                        // Get all files in book folder
                                        string[] filePaths = Directory.GetFiles(Constants.StorageStructure.EbookFolder);

                                        List<string> filePathForUploads = new List<string>();

                                        foreach (string filePath in filePaths)
                                        {
                                            string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(filePath);

                                            // Check if the file contains in database
                                            if (dao.CheckEbookExistByTitle(fileNameWithoutExtension))
                                            {
                                                filePathForUploads.Add(filePath);
                                            }
                                        }
                                        foreach (string filePathForUpload in filePathForUploads)
                                        {
                                            // Insert file to books folder
                                            InsertFile(Path.GetFileName(filePathForUpload),
                                                       Constants.CommonCharacters.Empty, oldfolder.Id,
                                                       Constants.textPlain,
                                                       filePathForUpload);
                                        }
                                    }
                                }
                                else if (oldfolder.Title == Constants.DatFileName)
                                {
                                    if (Utilities.CheckFileExist(Constants.StorageStructure.ApplicationData))
                                    {
                                        // Overwrite AeAppData.dat
                                        OverwriteFile(oldfolder.Id, Constants.DatFileName,
                                                      Constants.CommonCharacters.Empty, Constants.octetStream,
                                                      Constants.StorageStructure.ApplicationData);
                                    }
                                }
                            }
                        }
                    }
                    if (!ExistFlag)
                    {
                        // Create AeReaderData folder
                        File aereaderdatafolder = CreateFolder(Constants.StorageStructure.RootFolder,
                                                               Constants.CommonCharacters.Empty, null,
                                                               Constants.vndGoogleApps);
                        File aereaderdatafile = _service.Files.Insert(aereaderdatafolder).Fetch();

                        // Create Books folder
                        File booksfolder = CreateFolder(Constants.StorageStructure.Ebook,
                                                        Constants.CommonCharacters.Empty, aereaderdatafile.Id,
                                                        Constants.vndGoogleApps);
                        File bookparentfolder = _service.Files.Insert(booksfolder).Fetch();

                        // Get all files in book folder
                        string[] filePaths = Directory.GetFiles(Constants.StorageStructure.EbookFolder);

                        List<string> filePathForUploads = new List<string>();

                        foreach (string filePath in filePaths)
                        {
                            string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(filePath);

                            // Check if the file contains in database
                            if (dao.CheckEbookExistByTitle(fileNameWithoutExtension))
                            {
                                filePathForUploads.Add(filePath);
                            }
                        }
                        foreach (string filePathForUpload in filePathForUploads)
                        {
                            // Insert file to books folder
                            InsertFile(Path.GetFileName(filePathForUpload), Constants.CommonCharacters.Empty,
                                       bookparentfolder.Id, Constants.textPlain,
                                       filePathForUpload);
                        }

                        // Create AeAppData.dat
                        InsertFile(Constants.DatFileName, Constants.CommonCharacters.Empty, aereaderdatafile.Id,
                                   Constants.octetStream,
                                   Constants.StorageStructure.ApplicationData);
                    }
                    Utilities.ShowWarningMesbox(ResourceReader.GetResourceContent(UploadDone));
                }
            }
            catch (Exception ex)
            {
                Utilities.ShowWarningMesbox(ResourceReader.GetResourceContent(UploadProblem));
            }
        }

        /**
        * Print files belonging to a folder in google drive.
        *
        */

        private List<File> PrintFilesInFolder(
            String folderId)
        {
            List<File> file = new List<File>();
            ChildrenResource.ListRequest request = _service.Children.List(folderId);

            do
            {
                try
                {
                    ChildList children = request.Fetch();

                    foreach (ChildReference child in children.Items)
                    {
                        file.Add(_service.Files.Get(child.Id).Fetch());
                    }
                    request.PageToken = children.NextPageToken;
                }
                catch (Exception e)
                {
                    request.PageToken = null;
                }
            } while (!String.IsNullOrEmpty(request.PageToken));
            return file;
        }

        private File CreateFolder(String title, String description, String parentId, String mimeType)
        {
            // File's metadata.
            File body = new File();
            body.Title = title;
            body.Description = description;
            body.MimeType = mimeType;

            // Set the parent folder.
            if (!String.IsNullOrEmpty(parentId))
            {
                body.Parents = new List<ParentReference>() {new ParentReference() {Id = parentId}};
            }

            return body;
        }

        #endregion

        #region download

        /// <summary>
        /// Download App data from Google Drive
        /// </summary>
        public void DownloadHandler(MainWindow _parent, DAO _dao, FileHandler _fileHandler)
        {
            try
            {
                List<File> lstfile = new List<File>();
                // Get all file from google drive
                while (true)
                {
                    lstfile = RetrieveAllFiles();
                    if (lstfile == null && _continueCheck == 1)
                    {
                        _authCode = Constants.CommonCharacters.Empty;
                    }
                    else
                    {
                        break;
                    }
                }
                if (_continueCheck == 1)
                {
                    // Delete current directory if exist
                    if (Directory.Exists(Constants.StorageStructure.RootFolder))
                    {
                        Delete(new DirectoryInfo(Constants.StorageStructure.RootFolder));
                    }

                    // Create new directory
                    Directory.CreateDirectory(Constants.StorageStructure.EbookFolder);
                    Directory.CreateDirectory(Constants.StorageStructure.TempFolder);

                    foreach (File file in lstfile)
                    {
                        if (file.Title == Constants.StorageStructure.RootFolder)
                        {
                            List<File> lstAeReaderDatafile = PrintFilesInFolder(file.Id);
                            foreach (File aeReaderDatafile in lstAeReaderDatafile)
                            {
                                if (aeReaderDatafile.Title == Constants.StorageStructure.Ebook)
                                {
                                    List<File> lstBookFile = PrintFilesInFolder(aeReaderDatafile.Id);
                                    foreach (File bookFile in lstBookFile)
                                    {
                                        Stream stream = DownloadFile(bookFile);
                                        WriteToFile(stream,
                                                    Constants.StorageStructure.EbookFolder +
                                                    Constants.CommonCharacters.SlashString + bookFile.Title);
                                    }
                                }
                                else if (aeReaderDatafile.Title == Constants.DatFileName)
                                {
                                    Stream stream = DownloadFile(aeReaderDatafile);
                                    WriteToFile(stream, Constants.StorageStructure.ApplicationData);
                                }
                            }
                        }
                    }
                    if (Utilities.CheckFileExist(Constants.StorageStructure.ApplicationData))
                    {
                        _dao.Dto = _fileHandler.ReadFile(Constants.StorageStructure.ApplicationData);
                        _parent.Dto = _dao.Dto;
                    }
                    _parent.CloseAllTab();
                    Utilities.ShowWarningMesbox(ResourceReader.GetResourceContent(DownloadDone));
                }
            }
            catch (Exception ex)
            {
                Utilities.ShowWarningMesbox(ResourceReader.GetResourceContent(DownloadProblem));
            }
        }


        private void Delete(DirectoryInfo directory)
        {
            foreach (FileInfo file in directory.GetFiles()) file.Delete();
            foreach (DirectoryInfo subDirectory in directory.GetDirectories()) subDirectory.Delete(true);
        }

        private void WriteToFile(Stream stream, string filepath)
        {
            // create a write stream
            FileStream writeStsream = new FileStream(filepath, FileMode.Create,
                                                     FileAccess.Write);
            // write to the stream
            byte[] buffer = new byte[8*1024];
            int len;
            while ((len = stream.Read(buffer, 0, buffer.Length)) > 0)
            {
                writeStsream.Write(buffer, 0, len);
            }
        }

        // Download a file
        private Stream DownloadFile(File file)
        {
            if (!String.IsNullOrEmpty(file.DownloadUrl))
            {
                try
                {
                    HttpWebRequest request = (HttpWebRequest) WebRequest.Create(
                        new Uri(file.DownloadUrl));
                    _auth.ApplyAuthenticationToRequest(request);
                    HttpWebResponse response = (HttpWebResponse) request.GetResponse();
                    if (response.StatusCode == HttpStatusCode.OK)
                    {
                        return response.GetResponseStream();
                    }
                    else
                    {
                        return null;
                    }
                }
                catch (Exception e)
                {
                    return null;
                }
            }
            else
            {
                // The file doesn't have any content stored on Drive.
                return null;
            }
        }

        #endregion

        #region Util

        private List<File> RetrieveAllFiles()
        {
            List<File> result = new List<File>();
            FilesResource.ListRequest request = _service.Files.List();

            do
            {
                try
                {
                    FileList files = request.Fetch();

                    // Add the results to the result object and advance the page token. 
                    // The page token allows the results to be streamed back to us.
                    result.AddRange(files.Items);
                    request.PageToken = files.NextPageToken;
                }
                catch (ProtocolException e)
                {
                    // May want to log this or do something with it other than just dumping to the console.
                    Utilities.ShowWarningMesbox(ResourceReader.GetResourceContent(AuthcodeWrong));
                    return null;
                }
                catch (Exception e)
                {
                    return null;
                }

                // Keep doing this until the next page token is null. (Meaning there are no more pages to send)
            } while (!String.IsNullOrEmpty(request.PageToken));

            // Return the resulting list
            return result;
        }

        private File OverwriteFile(String fileId, String newTitle,
                                   String newDescription, String newMimeType, String newFilename)
        {
            try
            {
                // First, retrieve the file from the Google Drive.
                File file = _service.Files.Get(fileId).Fetch();

                // Set the file's new metadata.
                file.Title = newTitle;
                file.Description = newDescription;
                file.MimeType = newMimeType;

                // Get the file's new content and read it into a memory stream
                byte[] byteArray = System.IO.File.ReadAllBytes(newFilename);
                MemoryStream stream = new System.IO.MemoryStream(byteArray);

                // Call the Update API method passing in the updated information.
                FilesResource.UpdateMediaUpload request = _service.Files.Update(file, fileId, stream, newMimeType);

                request.NewRevision = true;
                // Execute the update
                request.Upload();

                // Get the response back from Google Drive and set the updatedFile object to the returned File informational object
                File updatedFile = request.ResponseBody;
                // Return the updated file object so the caller has a handle on it.
                return updatedFile;
            }
            catch (Exception e)
            {
                // May want to log this or do something with it other than just dumping to the console.
                return null;
            }
        }

        private File InsertFile(String title, String description, String parentId,
                                String mimeType, String filename)
        {
            // File's metadata.
            File body = new File();
            body.Title = title;
            body.Description = description;
            body.MimeType = mimeType;

            // Set the parent folder.
            if (!String.IsNullOrEmpty(parentId))
            {
                body.Parents = new List<ParentReference>() {new ParentReference() {Id = parentId}};
            }

            // Load the File's content and put it into a memory stream
            byte[] byteArray = System.IO.File.ReadAllBytes(filename);
            MemoryStream stream = new MemoryStream(byteArray);

            try
            {
                FilesResource.InsertMediaUpload request = _service.Files.Insert(body, stream, mimeType);
                request.Upload();

                // Set the file object to the response of the upload
                File file = request.ResponseBody;

                // return the file object so the caller has a reference to it.
                return file;
            }
            catch (Exception e)
            {
                // May want to log this or do something with it other than just dumping to the console.
                return null;
            }
        }

        private Boolean validFile(string filepath, string filepathDB)
        {
            if (filepath.Equals(filepathDB))
            {
            }
            return true;
        }

        #endregion

        #endregion
    }
}